#!/usr/bin/env php
<?php

define('CONFIG_FILE', 'magento-deploy-config.php');

if(file_exists(__DIR__ . '/' . CONFIG_FILE)) {
	require __DIR__ . '/' . CONFIG_FILE;
}

/**
 * 需要提供文件及目录:
 * .origin.git
 * .gen.git
 * .origin.ignore
 * .gen.ignore
 *
 * Class MagentoDeployBuff
 */
class MagentoDeployBuff
{
	//目标目录路径
    const DEPLOY_DIR = "";
    //PHP命令行名称
    const PHP_COMMANDLINE_NAME = "php";
    
    const GEN_GITIGNORE_FILENAME = '.gen.gitignore';
    const ORIGIN_GITIGNORE_FILENAME = '.origin.gitignore';
    const GEN_GIT_REPONAME = '.gen.git';
    const GEN_GIT_BARE_REPONAME = '.gen.bare.git';
    const ORIGIN_GIT_REPONAME = '.origin.git';
    
    protected $_root;
    protected $_commandArgInfo ;
    protected $_genGitIgnoreTemplate;
    protected $_deployDir;
    
    protected function _help() {
        $comments = [];
        
        foreach ($this->_commandArgInfo as $command => $comment) {
            $command = trim($command);
            $commandKey = rtrim($command, ':');
            
            if(substr($command, -2) == '::') {
                $comments ["--{$commandKey}=[<option>]"] = $comment;
            } else if(substr($command, -1) == ':') {
                $comments ["--{$commandKey}=<value>"] = $comment;
            } else {
                $comments ["--{$commandKey}"] = $comment;
            }
        }
        
        $commentKeys = array_keys($comments);
        $maxlength = max(array_map(function ($row) {return strlen($row);}, $commentKeys));
        
        $genGit = self::GEN_GIT_REPONAME;
        $genIgnore = self::GEN_GITIGNORE_FILENAME;
        $genBareGit = self::GEN_GIT_BARE_REPONAME;
        $originGit = self::ORIGIN_GIT_REPONAME;
        $originIgnore = self::ORIGIN_GITIGNORE_FILENAME;
        $currentFile = basename(__FILE__);
        
        echo <<<text
首先准备2个相同的代码目录A和B, A目录用于web运行, B用于编译magento.
A不能有git仓库(会自动生成), 也就是不能有 .git 目录
B必须有 .git 且是正常开发的版本管理仓库

在此脚本文件中先配置好 目标目录路径 和 PHP命令行名称 分别是类的 DEPLOY_DIR 和 PHP_COMMANDLINE_NAME 常量
配置好之后再 B 目录中运行 $currentFile --init 初始化项目相关环境

项目初始化完成之后就可以通过相关命令完成代码部署任务.
一般开发流程: 在C目录开发完成提交代码, 在B目录执行相关命令, 完成magento代码生成, 手动(自己git pull)或者自动(查看下方参数说明)部署到A目录
一般地, 可能存在magento编译过程中出错, 可以选择分2条命令执行: 执行 $currentFile 发现没有错误之后执行 $currentFile --only-deploy

示例命令:
$currentFile 清除magento 缓存, 执行upgrade, 编译di, static-content. 提交生成文件到临时仓库
$currentFile --deploy 清除magento 缓存, 执行upgrade, 编译di, static-content. 提交生成文件到临时仓库. 将目标目录更新到当前临时仓库版本
$currentFile --only-deploy 提交生成文件到临时仓库. 将目标目录更新到当前临时仓库版本
$currentFile --fetch-data 从开发仓库中拉取最新master代码, 并切换到master分支 然后执行 $currentFile 操作
$currentFile --fetch-data --branch=testbranch 从开发仓库中拉取最新testbranch分支的代码, 并切换到testbranch分支 然后执行 $currentFile 操作


环境目录或文件解析:
$originGit 目录用于存在原始远程仓库, 代码提交等在此仓库中
$originIgnore 文件存放原始远程仓库 ignore 文件
$genGit 目录用于存放临时仓库, 生成的代码文件将提交到此仓库中, 此远程仓库最好是本地仓库
$genIgnore 此文件用于存放临时仓库的 ignore 文件
$genBareGit 此目录是临时仓库

其他命令参考:


text;
        
        echo implode("\n", array_map(function($k, $v) use ($maxlength) { return sprintf("%-{$maxlength}s %s", $k, $v); }, $commentKeys, $comments));
        echo "\n";
        exit;
    }
    
    public function __construct()
    {
        $this->_deployDir = defined('DEPLOY_DIR') ? DEPLOY_DIR : self::DEPLOY_DIR;
        
        $this->_commandArgInfo = [
            'init' => '初始化环境',
            'branch::' => '可选分支, 默认master, 需要开启带上 --fetch-data 才会生效',
            'deploy' => '部署到目标环境: ' . realpath($this->_deployDir),
            'only-deploy' => '只部署目标环境, 不生成代码',
            'skip-di' => '跳过di编译',
            'skip-static' => '跳过静态内容生成',
            'fetch-data' => '从远程分支中 --branch 参数指定的分支中拉取最新数据后再开始生成magento代码',
            'to-origin' => '单独切换仓库到origin',
            'to-gen' => '单独切换仓库到gen',
            'show-repo' => '查看当前repo',
            'regen-gitignore' => '重新生成.gen.gitignore',
            'use-reset' => '使用git reset更新目标代码',
            'help' => '你现在看到的就是这个',
        ];
        
        $this->_genGitIgnoreTemplate =
			self::GEN_GITIGNORE_FILENAME . "\n" .
			self::ORIGIN_GITIGNORE_FILENAME . "\n" .
			self::GEN_GIT_REPONAME . "\n" .
			self::GEN_GIT_BARE_REPONAME . "\n" .
			self::ORIGIN_GIT_REPONAME . "\n" .
			"/var/log\n" .
			"/var/cache\n" .
            "/var/report\n" .
			"/.gitignore\n" .
			"/".CONFIG_FILE."\n" .
			"/pub/media\n" .
			basename(__FILE__)
		;
    }
    
    public function run() {
        error_reporting(E_ALL ^ E_NOTICE);
        ini_set('implicit_flush', 1);
        ini_set('output_buffering', 0);
    
        $this->_root = __DIR__;
        
        try {
            $this->_processing();
        } catch (Exception $e) {
            echo "\n[error] {$e->getMessage()} \n";
        }
    }
    
    /**
     * @throws Exception
     */
    protected function _init() {
    	$genBareGitRemote = $this->_root . '/' . self::GEN_GIT_BARE_REPONAME;
    	
    	if(!is_dir(self::ORIGIN_GIT_REPONAME)) {
    		if(!is_dir('.git')) {
    			$this->outputerr('缺少 .git 目录');
    			die;
			}
			
			rename('.git', self::ORIGIN_GIT_REPONAME);
		}
    
        if(!is_dir(self::GEN_GIT_BARE_REPONAME)) {
            mkdir(self::GEN_GIT_BARE_REPONAME);
            chdir(self::GEN_GIT_BARE_REPONAME);
            
            $this->runGitCommand('init --bare');
            
            chdir($this->_root);
        }
		
		if(!is_dir(self::GEN_GIT_REPONAME)) {
    		$dirname = self::GEN_GIT_REPONAME . mt_rand();
    		mkdir($dirname);
    		chdir($dirname);
    		
    		$this->runGitCommand('init');
    		$this->runGitCommand('remote add origin ' . $genBareGitRemote);
    		$this->runCommand("mv .git ../" . self::GEN_GIT_REPONAME);
    		
    		chdir($this->_root);
    		rmdir($dirname);
		}
		
		if(!file_exists(self::ORIGIN_GITIGNORE_FILENAME)) {
    		if(file_exists('.gitignore')) {
    			rename('.gitignore', self::ORIGIN_GITIGNORE_FILENAME);
			} else {
    			file_put_contents(self::ORIGIN_GITIGNORE_FILENAME, '');
			}
		}
    
        if(!file_exists(self::GEN_GITIGNORE_FILENAME)) {
			file_put_contents(self::GEN_GITIGNORE_FILENAME, $this->_genGitIgnoreTemplate);
        }
        
        $targetPath = realpath($this->_deployDir);
    	$targetGit = "$targetPath/.git";
    	
    	if($targetPath == false) {
    		$this->outputerr('目标路径不存在:' . $this->_deployDir);
    		exit;
		}
    	
        if(!file_exists($targetGit)) {
    		chdir($targetPath);
            $this->runGitCommand('init');
            $this->runGitCommand('remote add origin ' . $genBareGitRemote);
            chdir($this->_root);
		}
        
        exit;
	}
    
    
    
    public function outputerr($msg) {
    	echo "[ERROR] $msg\n";
	}
 
	protected function _regenerateGenGitIgnore() {
		file_put_contents(__DIR__.'/'.self::GEN_GITIGNORE_FILENAME, $this->_genGitIgnoreTemplate);
	}
	
    /**
     * @throws Exception
     */
    protected function _processing() {
        $args = getopt(null, array_keys($this->_commandArgInfo));
        
        if(isset($args['help'])) {
            $this->_help();
        }
        
        if(isset($args['regen-gitignore'])) {
        	$this->_regenerateGenGitIgnore();
        	return;
		}
        
        if(isset($args['init'])) {
        	$this->_init();
		}
        
        if(isset($args['to-origin'])) {
            $this->swtichToOriginGit();
            exit;
        }
    
        if(isset($args['to-gen'])) {
            $this->swtichToGenGit();
            exit;
        }
    
        if(isset($args['show-repo'])) {
            $gitPath = realpath('.git');
            
            if($gitPath == realpath(self::GEN_GIT_REPONAME)) {
                echo "gen\n";
            } else if($gitPath == realpath(self::ORIGIN_GIT_REPONAME)) {
                echo "origin\n";
            }
            exit;
        }
        
        $branch = $args['branch'];
        $branch = $branch ? $branch : "master";
        
        if(!isset($args['only-deploy'])) {
            if(isset($args['fetch-data'])) {
                $this->output("Switch to origin git repo...");
                $this->swtichToOriginGit();
                
                $this->output("[origin repo] Fetching update ($branch)");
                $this->runGitCommand("fetch origin $branch");
                $this->runGitCommand("stash");
                $this->runGitCommand("checkout $branch");
                $this->output("[origin repo] Applying update ($branch)");
                $this->runGitCommand("reset --hard origin/$branch");
            }
            
    
            $this->output("[Magento] Cleaning cache");
            $this->runMagentoCommand("cache:clean");
            $this->output("[Magento] Upgrading module");
            $this->runMagentoCommand("setup:upgrade");
            
            if(!isset($args['skip-di'])) {
                $this->output("[Magento] Compiling di");
                $this->runMagentoCommand("setup:di:compile");
			}
   
			if(!isset($args['skip-static'])) {
                $this->output("[Magento] Deploying static content");
                $this->runMagentoCommand("setup:static-content:deploy");
			}
        } else {
            $args['deploy'] = 1;
        }
    
        $this->output("Switch to gen git repo...");
        $this->swtichToGenGit();
    
        $this->output("[gen repo] Adding files to repo");
        $this->runGitCommand("add -A");
        $this->output("[gen repo] Commit change to repo");
        $this->runGitCommand("commit -a -m '{$this->generateGitComment()}'");
        $this->output("[gen repo] Push to remote");
        $this->runGitCommand("push origin master");
        
        if(isset($args['deploy'])) {
            $this->output('Deploy to target directory');
            
            if(!file_exists($this->_deployDir)) {
                $this->output("[ERROR] Deploy dir not exists");
                die;
            }
            
            chdir($this->_deployDir);
            $this->output("Current Directory: " . realpath(getcwd()));
            
            if(realpath(getcwd()) != realpath($this->_deployDir)) {
                $this->output("[ERROR] Directory change fail");
                die;
            }
    
            $this->output("[Target] Fetch master");
            $this->runGitCommand('fetch origin master');
            $this->output("[Target] Apply master");
            if(isset($args['use-reset'])) {
                $this->runGitCommand('reset --hard origin/master');
			} else {
                $this->runGitCommand('pull origin master');
			}
            
            $this->output("[Target] Changing directory permisstion");
            $this->runCommand('chmod 777 -R var/');
            $this->runCommand('chmod 777 -R pub/');
        }
        
        $this->output("Finish");
    }
    
    public function generateGitComment() {
        return "Updated at " . date("Ymd-His") . '-' .mt_rand();
    }
    
    /**
     * @param $command
     * @return bool|string
     * @throws Exception
     */
    public function runGitCommand($command) {
        return $this->runCommand("git $command");
    }
    
    /**
     * @param $command
     * @return bool|string
     * @throws Exception
     */
    public function runMagentoCommand($command) {
        $phpexector = defined('PHP_COMMANDLINE_NAME') ? PHP_COMMANDLINE_NAME : self::PHP_COMMANDLINE_NAME;
        return $this->runCommand("$phpexector \"{$this->_root}/bin/magento\" " . $command);
    }
    
    public function output($msg) {
        echo "===== $msg\n";
    }
    
    
    /**
     * @param $command
     * @return bool|string
     * @throws Exception
     */
    public function runCommand($command) {
        echo "-> $command\n";
        $result = system($command);
        
        if(false === $result) {
            throw new Exception("Command execution failed: $command");
        }
        
        return $result;
    }
    
    public function swtichToOriginGit() {
        $this->unlink("{$this->_root}/.gitignore");
        $this->unlink("{$this->_root}/.git");
    
        if(!file_exists($this->getOriginIgnoreFilePath())) {
            $this->_dieError("缺少 " . $this->getOriginIgnoreFilePath() . '文件');
        }
        
        if(!file_exists($this->getOriginGitRepoPath())) {
            $this->_dieError("缺少 " . $this->getOriginGitRepoPath());
        }
        
        symlink($this->getOriginGitRepoPath(), '.git');
        symlink($this->getOriginIgnoreFilePath(), '.gitignore');
    }
    
    public function unlink($file) {
        if(file_exists($file)) {
            unlink($file);
        }
    }
    
    public function swtichToGenGit() {
    	if(file_exists("{$this->_root}/.gitignore")) {
            unlink("{$this->_root}/.gitignore");
		}
  
		if(file_exists("{$this->_root}/.git")) {
            unlink("{$this->_root}/.git");
		}
        
    
        if(!file_exists($this->getGenIgnoreFilePath())) {
            $this->_dieError("缺少 " . $this->getGenIgnoreFilePath() . '文件');
        }
    
        if(!file_exists($this->getGenGitRepoPath())) {
            $this->_dieError("缺少 " . $this->getGenGitRepoPath());
        }
    
        symlink($this->getGenGitRepoPath(), '.git');
        symlink($this->getGenIgnoreFilePath(), '.gitignore');
    }
    
    public function getOriginIgnoreFilePath() {
        return "{$this->_root}/" . self::ORIGIN_GITIGNORE_FILENAME;
    }
    
    public function getOriginGitRepoPath() {
        return "{$this->_root}/" . self::ORIGIN_GIT_REPONAME;
    }
    
    public function getGenIgnoreFilePath() {
        return "{$this->_root}/" . self::GEN_GITIGNORE_FILENAME;
    }
    
    public function getGenGitRepoPath() {
        return "{$this->_root}/" . self::GEN_GIT_REPONAME;
    }
    
    protected function _dieError($msg) {
        echo "[ERROR] $msg\n";
        die;
    }
}

(new MagentoDeployBuff())->run();